﻿using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;

using iTool.ClusterComponent;

using Microsoft.Data.Sqlite;

namespace iTool.Cloud.DataSearch.ServiceProvider
{
    public class IndexFieldManageService : iToolServiceStorageBase<MemoryStream>, IIndexFieldManageService
    {
        bool isLocal = false;
        MemoryStream _State;
        MemoryStream ProxyState
        {
            get
            {
                return isLocal ? _State : State;
            }
            set
            {
                if (isLocal)
                {
                    _State = value;
                }
                else
                {
                    State = value;
                }
            }
        }

        // AUTOINCREMENT 自增
        const string PRIMARY_KEY_KEYWORD = "FIELDNAME";
        const string VALUE_NOT_NULL = "NOT NULL";
        const string PRIMARY_KEY_KEYWORD_TYPE = "VARCHAR PRIMARY KEY";
        const string CREATE_FIELD_SQL = "{0} {1} {2}";

        private string? field;
        const string DirectoryPath = "./Storage/Index";

        string DataSource = DirectoryPath + "/{0}.index";
        string connectionString;

        long rowid = 0;

        public IndexFieldManageService() 
        {
            if (!Directory.Exists(DirectoryPath))
                Directory.CreateDirectory(DirectoryPath);
        }

        public async override Task OnActivateAsync()
        {
            var service = this.GetService<ILuceneDirectoryService>(this.GetLongKey());
            this.field = this.GetStringKey();
            _ = service.AddFileAsync(field);
            this.connectionString = new SqliteConnectionStringBuilder
            {
                DataSource = String.Format(this.DataSource, this.GetLongKey()),
                Mode = SqliteOpenMode.ReadWriteCreate,
                Cache = SqliteCacheMode.Shared,
                Pooling = true
            }.ToString();

            string sql = @$"CREATE TABLE IF NOT EXISTS FileContents({string.Format(CREATE_FIELD_SQL, PRIMARY_KEY_KEYWORD, PRIMARY_KEY_KEYWORD_TYPE, VALUE_NOT_NULL)},CONTENT BLOG,LASTDATETIME LONG NOT NULL)";
            sql += ';';
            sql += @$"CREATE TABLE IF NOT EXISTS FieldsMetaData({string.Format(CREATE_FIELD_SQL, PRIMARY_KEY_KEYWORD, PRIMARY_KEY_KEYWORD_TYPE, VALUE_NOT_NULL)})";

            using (var connection = new SqliteConnection(this.connectionString))
            {
                connection.Open();
                using (SqliteCommand command = new SqliteCommand(sql, connection))
                {
                    command.ExecuteNonQuery();
                }
            }

            await base.OnActivateAsync();
        }

        public async Task DisposeAsync()
        {
            await this.WriteStateAsync();
        }

        public async Task FlushBufferAsync(long position, byte[] segment, int length)
        {
            try
            {
                if (length <= position)
                {
                    this.ProxyState.Seek(position - length, SeekOrigin.Begin);
                    //if (this.isLocal)
                    //    this._State.Seek(position - length, SeekOrigin.Begin);
                    //else
                    //    this.State.Seek(position - length, SeekOrigin.Begin);
                }
                else
                {
                    Console.WriteLine($"2 Seek:Length:{this.ProxyState.Length},position:{position},length:{length}");
                    //this.ProxyState.Seek(position, SeekOrigin.Begin);
                }
                //this.ProxyState.Seek(position, SeekOrigin.Begin);

                this.ProxyState.Write(segment, 0, length);
                //if (this.isLocal)
                //    this._State.Write(segment, 0, length);
                //else
                //    this.State.Write(segment, 0, length);
                //await this.WriteStateAsync();
                await Task.CompletedTask;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"position：{position}，length:{length}, val:{position - length};" + ex);
                throw;
            }
        }

        public Task<long> LengthAsync()
        {
            return Task.FromResult(this.ProxyState.Length);
        }

        public Task<byte[]> ReadInternalAsync(long position, int offset, int length)
        {
            //if (this.isLocal)
            //    this._State.Position = position;
            //else
            //    this.State.Position = position;

            this.ProxyState.Position= position;

            byte[]? buff = null;
            if (this.isLocal)
            {
                buff = ArrayPool<byte>.Shared.Rent(length);
            }
            else
            {
                buff = new byte[length];
            }

            this.ProxyState.Read(buff, 0, length);

            //if (this.isLocal)
            //    this._State.Read(buff, 0, length);
            //else
            //    this.State.Read(buff, 0, length);

            return Task.FromResult<byte[]>(buff);
        }

        private string GetTimeStamp()
        {
            TimeSpan ts = DateTime.Now - new DateTime(2022, 03, 12, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }

        protected override async Task ClearStateAsync()
        {
            await this.ProxyState.DisposeAsync();
            this.ProxyState = new MemoryStream();

            await using (var connection = new SqliteConnection(this.connectionString))
            {
                await connection.OpenAsync();
                var command = new SqliteCommand($"delete from FileContents WHERE {PRIMARY_KEY_KEYWORD} = $key", connection);
                command.Parameters.AddWithValue("$key", this.field);
                await command.ExecuteNonQueryAsync();
            }
        }

        protected override Task ReadStateAsync()
        {
            // 检查存不存在，存在则查询 否则创建
            using (var connection = new SqliteConnection(this.connectionString))
            {
                connection.Open();
                var command = new SqliteCommand($"SELECT rowid,Content FROM FileContents WHERE {PRIMARY_KEY_KEYWORD} = $key", connection);
                command.Parameters.AddWithValue("$key", this.field);
                var reader = command.ExecuteReader();

                this.ProxyState = new MemoryStream();

                if (!reader.HasRows)
                {
                    command.Dispose();
                    command = connection.CreateCommand();
                    command.CommandText = $"INSERT INTO FileContents({PRIMARY_KEY_KEYWORD},Content,LASTDATETIME) VALUES ($key,$content,$date);SELECT last_insert_rowid();";
                    command.Parameters.AddWithValue("$key", this.field);
                    command.Parameters.AddWithValue("$content", this.ProxyState.ToArray());
                    command.Parameters.AddWithValue("$date", this.GetTimeStamp());
                    long.TryParse(command.ExecuteScalar()?.ToString(), out this.rowid);
                    return Task.CompletedTask;
                }

                while (reader.Read())
                {
                    this.rowid = reader.GetInt64(0);
                    using (var readStream = reader.GetStream(1))
                    {
                        readStream.CopyTo(this.ProxyState);
                        //if (this.isLocal)
                        //    readStream.CopyTo(this._State);
                        //else
                        //    readStream.CopyTo(this.State);
                    }
                }
            }

            return Task.CompletedTask;
        }

        protected override async Task WriteStateAsync()
        {
            await using (var connection = new SqliteConnection(this.connectionString))
            {
                await connection.OpenAsync();

                //using (var writeStream = new SqliteBlob(connection, "FileContents", "Content", this.rowid))
                //{
                //    await this.ProxyState.CopyToAsync(writeStream);
                //}

                //var command = new SqliteCommand($"update FileContents set Content=$content,LASTDATETIME=$date WHERE {PRIMARY_KEY_KEYWORD} = $key", connection);
                var command = new SqliteCommand($"update FileContents set Content=$content,LASTDATETIME=$date WHERE rowid = $key", connection);
                command.Parameters.AddWithValue("$key", this.rowid);
                command.Parameters.AddWithValue("$content", this.ProxyState.ToArray());
                command.Parameters.AddWithValue("$date", this.GetTimeStamp());
                await command.ExecuteNonQueryAsync();
            }
        }

        public async Task DeleteFileAsync()
        {
            this.ProxyState = new MemoryStream();
            await this.ClearStateAsync();
        }

        public async Task FlushAsync()
        {
            await this.WriteStateAsync();
        }

        public void OnActivateAsync(long hash, string name)
        {
            this.isLocal = true;
            _ = IDirectoryServiceFactory<LuceneDirectoryService>.GetService(hash, String.Empty).AddFileAsync(name);

            this.field = name;
            this.connectionString = new SqliteConnectionStringBuilder
            {
                DataSource = String.Format(this.DataSource, hash),
                Mode = SqliteOpenMode.ReadWriteCreate,
                Cache = SqliteCacheMode.Shared,
                Pooling = true
            }.ToString();

            string sql = @$"CREATE TABLE IF NOT EXISTS FileContents({string.Format(CREATE_FIELD_SQL, PRIMARY_KEY_KEYWORD, PRIMARY_KEY_KEYWORD_TYPE, VALUE_NOT_NULL)},CONTENT BLOG,LASTDATETIME LONG NOT NULL)";
            sql += ';';
            sql += @$"CREATE TABLE IF NOT EXISTS FieldsMetaData({string.Format(CREATE_FIELD_SQL, PRIMARY_KEY_KEYWORD, PRIMARY_KEY_KEYWORD_TYPE, VALUE_NOT_NULL)})";

            using (var connection = new SqliteConnection(this.connectionString))
            {
                connection.Open();
                using (SqliteCommand command = new SqliteCommand(sql, connection))
                {
                    command.ExecuteNonQuery();
                }
            }

            base.OnActivateAsync().Wait();
        }
    }
}
